﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Windows.Input;
using Workbench.Models;
using Workbench.Operations;
using Workbench.Shared;

namespace Workbench.Presentation
{
    public class ConcordancerViewModel : TabViewModelBase
    {
        public ConcordancerViewModel()
            : base("Concordancer")
        {
            PropertyChanged += ConcordancerViewModel_PropertyChanged;
            upCommand = new _UpCommand(this);
            downCommand = new _DownCommand(this);
            sortCommand = new _SortCommand(this);
        }

        #region Properties

        private ObservableCollection<WordWithNearWordsViewModel> words = new ObservableCollection<WordWithNearWordsViewModel>();
        public ObservableCollection<WordWithNearWordsViewModel> Words
        {
            get { return words; }
        }

        private ObservableCollection<WordSuggestionViewModel> suggestedWords = new ObservableCollection<WordSuggestionViewModel>();
        public ObservableCollection<WordSuggestionViewModel> SuggestedWords
        {
            get { return suggestedWords; }
        }

        private WordSuggestionViewModel selectedWordSuggestion;
        public WordSuggestionViewModel SelectedWordSuggestion
        {
            get { return selectedWordSuggestion; }
            set { selectedWordSuggestion = value; RaisePropertyChanged(); }
        }

        private ConcordancerSortingType sortingType;
        public ConcordancerSortingType SortingType
        {
            get { return sortingType; }
            set { sortingType = value; RaisePropertyChanged(); }
        }

        private SuggestionType suggestionType;
        public SuggestionType SuggestionType
        {
            get { return suggestionType; }
            set { suggestionType = value; RaisePropertyChanged(); }
        }

        private string currentSearchContent;
        public string CurrentSearchContent
        {
            get { return currentSearchContent; }
            set { currentSearchContent = value; RaiseThrottledPropertyChanged(200); }
        }

        private int sortingColumn;
        public int SortingColumn
        {
            get { return sortingColumn; }
            set { sortingColumn = value; RaisePropertyChanged(); }
        }

        #endregion Properties

        #region Commands

        #region UpCommand

        readonly _UpCommand upCommand;
        public ICommand UpCommand
        {
            get { return upCommand; }
        }

        private class _UpCommand : ICommand
        {
            readonly ConcordancerViewModel viewModel;

            public _UpCommand(ConcordancerViewModel viewModel)
            {
                this.viewModel = viewModel;

                viewModel.PropertyChanged += viewModel_PropertyChanged;
            }

            void viewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
            {
                if (string.Compare(e.PropertyName, "SortingColumn", false) == 0)
                {
                    RaiseCanExecuteChanged();
                }
            }

            void RaiseCanExecuteChanged()
            {
                this.CanExecuteChanged(null, EventArgs.Empty);
            }

            public bool CanExecute(object parameter)
            {
                return viewModel.SortingColumn < 5;
            }

            public void Execute(object parameter)
            {
                viewModel.SortingColumn++;
            }

            public event EventHandler CanExecuteChanged = (sender, args) => { };
        }

        #endregion UpCommand

        #region DownCommand

        readonly _DownCommand downCommand;
        public ICommand DownCommand
        {
            get { return downCommand; }
        }

        private class _DownCommand : ICommand
        {
            readonly ConcordancerViewModel viewModel;

            public _DownCommand(ConcordancerViewModel viewModel)
            {
                this.viewModel = viewModel;

                viewModel.PropertyChanged += viewModel_PropertyChanged;
            }

            void viewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
            {
                if (string.Compare(e.PropertyName, "SortingColumn", false) == 0)
                {
                    RaiseCanExecuteChanged();
                }
            }

            void RaiseCanExecuteChanged()
            {
                this.CanExecuteChanged(null, EventArgs.Empty);
            }

            public bool CanExecute(object parameter)
            {
                return viewModel.SortingColumn > -5;
            }

            public void Execute(object parameter)
            {
                viewModel.SortingColumn--;
            }

            public event EventHandler CanExecuteChanged = (sender, args) => { };
        }

        #endregion DownCommand

        #region SortCommand

        readonly _SortCommand sortCommand;
        public ICommand SortCommand
        {
            get { return sortCommand; }
        }

        private class _SortCommand : ICommand
        {
            readonly ConcordancerViewModel viewModel;

            public _SortCommand(ConcordancerViewModel viewModel)
            {
                this.viewModel = viewModel;

                viewModel.PropertyChanged += viewModel_PropertyChanged;
                viewModel.Words.CollectionChanged += Words_CollectionChanged;
            }

            void Words_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
            {
                RaiseCanExecuteChanged();
            }

            void viewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
            {
                if (string.Compare(e.PropertyName, "SortingColumn", false) == 0)
                {
                    RaiseCanExecuteChanged();
                }
            }

            void RaiseCanExecuteChanged()
            {
                if (CanExecuteChanged == null) return;

                this.CanExecuteChanged(null, EventArgs.Empty);
            }

            public bool CanExecute(object parameter)
            {
                if (viewModel.SortingColumn == 0) return false;

                var keySelector = GetColumnPropertySelector(viewModel.sortingColumn);
                if (keySelector == null) return false;

                return viewModel.words.Any(x => !string.IsNullOrWhiteSpace(keySelector(x)));
            }

            public void Execute(object parameter)
            {
                if (!CanExecute(parameter)) return;

                var propSelector = GetColumnPropertySelector(viewModel.SortingColumn);
                if (propSelector == null) return;

                var wordsViewModels = viewModel.Words.ToList();
                if (viewModel.SortingType == ConcordancerSortingType.Alphabetically)
                {
                    SortAlphabetically(wordsViewModels, propSelector);
                }
                else
                {
                    SortByFrequency(wordsViewModels, propSelector);
                }
                viewModel.Words.Clear();
                viewModel.Words.AddRange(wordsViewModels);
            }

            private void SortAlphabetically(List<WordWithNearWordsViewModel> wordsViewModels, Func<WordWithNearWordsViewModel, string> propSelector)
            {
                wordsViewModels.Sort((x, y) => string.Compare(propSelector(x), propSelector(y), StringComparison.CurrentCulture));
            }

            private void SortByFrequency(List<WordWithNearWordsViewModel> wordsViewModels, Func<WordWithNearWordsViewModel, string> propSelector)
            {
                Dictionary<string, int> cnts = new Dictionary<string, int>();
                string str = string.Empty;
                foreach (var wordViewModel in wordsViewModels)
                {
                    str = propSelector(wordViewModel);
                    if (str != null)
                    {
                        if (!cnts.ContainsKey(str))
                        {
                            cnts.Add(str, 0);
                        }
                        cnts[str]++;
                    }
                }
                wordsViewModels.Sort((x, y) =>
                {
                    int cntx = 0;
                    if (!string.IsNullOrEmpty(propSelector(x)))
                        cntx = cnts[propSelector(x)];
                    int cnty = 0;
                    if (!string.IsNullOrEmpty(propSelector(y)))
                        cnty = cnts[propSelector(y)];

                    return -(cntx.CompareTo(cnty));
                });
            }

            public event EventHandler CanExecuteChanged;

            public Func<WordWithNearWordsViewModel, string> GetColumnPropertySelector(int column)
            {
                switch (viewModel.SortingColumn)
                {
                    case 0:
                        return (x) => x.WordContent;
                    case 1:
                        return (x) => x.RightWord1;
                    case 2:
                        return (x) => x.RightWord2;
                    case 3:
                        return (x) => x.RightWord3;
                    case 4:
                        return (x) => x.RightWord4;
                    case 5:
                        return (x) => x.RightWord5;
                    case -1:
                        return (x) => x.LeftWord1;
                    case -2:
                        return (x) => x.LeftWord2;
                    case -3:
                        return (x) => x.LeftWord3;
                    case -4:
                        return (x) => x.LeftWord4;
                    case -5:
                        return (x) => x.LeftWord5;
                    default:
                        return null;
                }
            }
        }

        #endregion DownCommand

        #endregion Commands

        #region EventHandlers

        private void ConcordancerViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            switch (e.PropertyName)
            {
                case "SuggestionType":
                    if (CurrentSearchContent != null)
                    {
                        LoadSuggestions();
                    }
                    break;
                case "CurrentSearchContent":
                    if (CurrentSearchContent != null)
                    {
                        LoadSuggestions();
                    }
                    break;
                case "SelectedWordSuggestion":
                    if (SelectedWordSuggestion != null)
                    {
                        LoadNearWords();
                    }
                    break;
            }
        }

        #endregion EventHandlers

        #region Actions

        public void LoadNearWords()
        {
            Words.Clear();
            var result = ConcordanceOperations.GetNearWords(SelectedWordSuggestion.DataModel, SuggestionType);
            var wordViewModels = result.Select(x => new WordWithNearWordsViewModel(x));
            Words.AddRange(wordViewModels);
        }

        public void LoadSuggestions()
        {
            var result = ConcordanceOperations.GetSuggestions(CurrentSearchContent, SuggestionType);
            var viewModels = result.Select(x => new WordSuggestionViewModel(x));
            SelectedWordSuggestion = null;
            SuggestedWords.Clear();
            SuggestedWords.AddRange(viewModels);
        }

        public override void Load()
        {
        }

        public override void Unload()
        {
            Words.Clear();
            SuggestedWords.Clear();
            CurrentSearchContent = null;
            SortingColumn = 0;
        }

        #endregion Actions
    }

    [DebuggerDisplay("Content={Content}")]
    public class WordSuggestionViewModel : ViewModelBase
    {
        public WordSuggestionViewModel(WordSuggestionModel dataModel)
        {
            this.Content = dataModel.Content;
            this.RowId = dataModel.RowId;
            this.DataModel = dataModel;
        }

        public WordSuggestionModel DataModel;

        private string content;
        public string Content
        {
            get { return content; }
            set { content = value; RaisePropertyChanged(); }
        }

        private long rowId;
        public long RowId
        {
            get { return rowId; }
            set { rowId = value; RaisePropertyChanged(); }
        }
    }

    public class WordWithNearWordsViewModel : ViewModelBase
    {
        public WordWithNearWordsViewModel(WordWithNearWordsModel dataModel)
        {
            WordContent = dataModel.Word;
            if (dataModel.WordsToTheLeft.Count > 0)
                LeftWord1 = dataModel.WordsToTheLeft[0];
            if (dataModel.WordsToTheLeft.Count > 1)
                LeftWord2 = dataModel.WordsToTheLeft[1];
            if (dataModel.WordsToTheLeft.Count > 2)
                LeftWord3 = dataModel.WordsToTheLeft[2];
            if (dataModel.WordsToTheLeft.Count > 3)
                LeftWord4 = dataModel.WordsToTheLeft[3];
            if (dataModel.WordsToTheLeft.Count > 4)
                LeftWord5 = dataModel.WordsToTheLeft[4];

            if (dataModel.WordsToTheRight.Count > 0)
                RightWord1 = dataModel.WordsToTheRight[0];
            if (dataModel.WordsToTheRight.Count > 1)
                RightWord2 = dataModel.WordsToTheRight[1];
            if (dataModel.WordsToTheRight.Count > 2)
                RightWord3 = dataModel.WordsToTheRight[2];
            if (dataModel.WordsToTheRight.Count > 3)
                RightWord4 = dataModel.WordsToTheRight[3];
            if (dataModel.WordsToTheRight.Count > 4)
                RightWord5 = dataModel.WordsToTheRight[4];
            
            this.DataModel = dataModel;
        }

        public WordWithNearWordsModel DataModel;

        private string wordContent;
        public string WordContent
        {
            get { return wordContent; }
            set { wordContent = value; RaisePropertyChanged(); }
        }

        private string leftWord1;
        public string LeftWord1
        {
            get { return leftWord1; }
            set { leftWord1 = value; RaisePropertyChanged(); }
        }

        private string leftWord2;
        public string LeftWord2
        {
            get { return leftWord2; }
            set { leftWord2 = value; RaisePropertyChanged(); }
        }

        private string leftWord3;
        public string LeftWord3
        {
            get { return leftWord3; }
            set { leftWord3 = value; RaisePropertyChanged(); }
        }

        private string leftWord4;
        public string LeftWord4
        {
            get { return leftWord4; }
            set { leftWord4 = value; RaisePropertyChanged(); }
        }

        private string leftWord5;
        public string LeftWord5
        {
            get { return leftWord5; }
            set { leftWord5 = value; RaisePropertyChanged(); }
        }

        private string rightWord1;
        public string RightWord1
        {
            get { return rightWord1; }
            set { rightWord1 = value; RaisePropertyChanged(); }
        }

        private string rightWord2;
        public string RightWord2
        {
            get { return rightWord2; }
            set { rightWord2 = value; RaisePropertyChanged(); }
        }

        private string rightWord3;
        public string RightWord3
        {
            get { return rightWord3; }
            set { rightWord3 = value; RaisePropertyChanged(); }
        }

        private string rightWord4;
        public string RightWord4
        {
            get { return rightWord4; }
            set { rightWord4 = value; RaisePropertyChanged(); }
        }

        private string rightWord5;
        public string RightWord5
        {
            get { return rightWord5; }
            set { rightWord5 = value; RaisePropertyChanged(); }
        }

        private bool isSelected;
        public bool IsSelected
        {
            get { return isSelected; }
            set { isSelected = value; RaisePropertyChanged(); }
        }
    }
}
